﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ParserCore;
using NDimArrayNamespace;
using AtomicTypes;
using Memory_Access;
using QLabValues;
using System.Diagnostics;

namespace Engine
{
    public class EngineVisitor:IVisitor
    {
        // result(s) of last calculation
        public List<IQLabValue> _tempAns;

        // flags for continue and break statement
        private bool _continueFlag, _breakFlag, _isNestedFunc, _isFunctionHandle;
        
        private int _leftHandSideCount;

        private VarTable _variableTable;

        private FunctionTable _nestedFuncTable, _subFuncTable, _privateFuncTable;

        List<string> _needPreprocessing;

        public VarTable VariableTable
        {
            get
            {
                return _variableTable;
            }
            set
            {
                _variableTable = value;
            }
        }

        public FunctionTable NestedFuncTable
        {
            get
            {
                return _nestedFuncTable;
            }
            set
            {
                _nestedFuncTable = value;
            }
        }

        public FunctionTable SubFuncTable
        {
            get
            {
                return _subFuncTable;
            }
            set
            {
                _subFuncTable = value;
            }
        }

        public FunctionTable PrivateFuncTable
        {
            get
            {
                return _privateFuncTable;
            }
            set
            {
                _privateFuncTable = value;
            }
        }
                
        public EngineVisitor()
        {
            _tempAns = new List<IQLabValue>();

            _continueFlag = false;
            _breakFlag = false;

            _variableTable = new VarTable();
            _nestedFuncTable = new FunctionTable();
            _subFuncTable = new FunctionTable();
            _privateFuncTable = new FunctionTable();

            _needPreprocessing = new List<string>();
            _needPreprocessing.Add("filter");
            _needPreprocessing.Add("find");
            _needPreprocessing.Add("histc");
            _needPreprocessing.Add("sort");
            _needPreprocessing.Add("min");
            _needPreprocessing.Add("max");
            _needPreprocessing.Add("size");

            _leftHandSideCount = 0;
        }
              
        private void AddSubFunctions(BlockStatement function)
        {
                Debug.Assert(function.StatementList[0] is FunctionDefinitionStatement);
                FunctionDefinitionStatement mainFunction = (FunctionDefinitionStatement)(function.StatementList[0]);
                FunctionDefinitionStatement subfunction;

                for (int i=0; i<function.StatementList.Length-1; i++)
                {
                    Debug.Assert(function.StatementList[i+1] is FunctionDefinitionStatement);
                    subfunction = (FunctionDefinitionStatement)function.StatementList[i+1];
                    _subFuncTable.AddParsedFunction(subfunction);
                }
        }
 
        private void AddNestedFunctions(FunctionDefinitionStatement function)
        // does NOT go deeper than one level (does not insert nested functions of nested functions of the argument)
        {
            if  (!(function.FunctionBody is BlockStatement)) // no nested functions inside a single statement
                return;

            BlockStatement bodyBlock = (BlockStatement)(function.FunctionBody);
            foreach (Statement statement in bodyBlock.StatementList)
            // nested functions cannot be defined inside loops etc, so we do not have to perform a deeper check
            {
                if (statement is FunctionDefinitionStatement)
                    _nestedFuncTable.AddParsedFunction((FunctionDefinitionStatement)statement);
            }
        }

        private int[][] GetSubIndex(Expression[] argumentList)
        {
            _tempAns.Clear();
            List<int[]> subCoordinates=new List<int[]>();

            for (int i = 0; i < argumentList.Length; i++)
            {
                argumentList[i].Accept(this);
                switch (_tempAns[0].GetEnumType())
                {
                    case IQLabValueType.QBooleanNDimArray:
                        subCoordinates.Add(((QBooleanNDimArray)_tempAns[0]).ToIndexArray()); //TODO: fix it, this doesn't work like this
                        break;
                    case IQLabValueType.QCharNDimArray:
                        subCoordinates.Add(((QCharNDimArray)_tempAns[0]).ToIndexArray());
                        break;
                    case IQLabValueType.QInt64NDimArray:
                        subCoordinates.Add(((QInt64NDimArray)_tempAns[0]).ToIndexArray());
                        break;
                    case IQLabValueType.QUInt64NDimArray:
                        subCoordinates.Add(((QUInt64NDimArray)_tempAns[0]).ToIndexArray());
                        break;
                    case IQLabValueType.QFloat64NDimArray:
                        subCoordinates.Add(((QFloat64NDimArray)_tempAns[0]).ToIndexArray()); //TODO: fix it, this doesn't work like this (float is not int..)
                        break;
                    default:
                        throw new EngineException(i+". argument has invalid index type");
                }
                _tempAns.Clear();
            }

            return subCoordinates.ToArray();                                                                                         
        }

        private bool IsMatrix(IQLabValue variable)
        // not anonymous function or cell
        {
            return (variable.GetType() == typeof(QInt64NDimArray)) || (variable.GetType() == typeof(QUInt64NDimArray)) || (variable.GetType() == typeof(QFloat64NDimArray)) || (variable.GetType() == typeof(QCharNDimArray)) || (variable.GetType() == typeof(QBooleanNDimArray)) || (variable.GetType() == typeof(QStructValueNDimArray));
        }

        FunctionImplementation LookUpOtherTables(Identifier ID)
        // not i variable table, look up in others
        {
            FunctionImplementation calledFunction;
            calledFunction = _subFuncTable.LookUp(ID);
            if (calledFunction == null)
            {
                calledFunction = _privateFuncTable.LookUp(ID);
                if (calledFunction == null)
                {
                    calledFunction = EngineManager.Instance.currentFolderFuncTable.LookUp(ID);
                    if (calledFunction == null)
                    {
                        calledFunction = EngineManager.Instance.pathFuncTable.LookUp(ID);
                        if (calledFunction == null)
                        {
                            throw new EngineException(ID.ID+" not found");
                        }
                    }
                }
            }
            return calledFunction;
        }

        private bool AllZeros(int[] arr)
        {
            for (int i = 0; i < arr.Count(); i++)
                if (arr[i] != 0)
                    return false;
            return true;
        }

        private bool AllOnes(int[] arr) 
        {
            for (int i = 0; i < arr.Count(); i++)
                if (arr[i] != 1)
                    return false;
            return true;
        }

        private void LeftHandSideCount(List<IQLabValue> args, string func)
        {
            if (_needPreprocessing.Contains(func))
                args.Add(new QInt64NDimArray(new QInt64[1]{ new QInt64(_leftHandSideCount) }, new int[2]{ 1, 1}));
        }
       
        public void Visit(Constant exp)
        {
            _tempAns.Clear();

            switch (exp.ConstType)
            {
                case Constant.CType.String:
                    QChar[] tempQCharArray=new QChar[exp.Const.Length];
                    for (int i=0; i<exp.Const.Length; i++)
                        tempQCharArray[i]=new QChar(exp.Const[i]);
                    _tempAns.Add(new QCharNDimArray(tempQCharArray, new int[2]{ 1, exp.Const.Length }));
                    break;
                case Constant.CType.RealNumber:
                    _tempAns.Add(new QFloat64NDimArray(new QFloat64[1] { new QFloat64(Convert.ToDouble(exp.Const, System.Globalization.CultureInfo.InvariantCulture)) }, new int[2] { 1, 1 }));
                    break;
                case Constant.CType.ImaginaryNumber:
                    _tempAns.Add(new QFloat64NDimArray(new QFloat64[1] { new QFloat64(0, Convert.ToDouble(exp.Const, System.Globalization.CultureInfo.InvariantCulture)) }, new int[2] { 1, 1 }));
                    break;
                
            }
        }

        public void Visit(Identifier exp)
        {
            _tempAns.Clear();
            IQLabValue variable = _variableTable.LookUp(exp);
            if (variable != null)
                _tempAns.Add(variable);
            else
            {
                // if exp is not found in variable table it can be function call without arguments
                // HINT: konstante(pi, e...) su pozivi funkcija bez argumenata
                Call tempCall=new Call(exp, new Expression[]{} );
                tempCall.Accept(this);
            }
        }

        public void Visit(Call exp)
        {
            /* HINT: ako definisemo funkciju koja se zove kao BuiltIn funkcija, matlab izbacuje warning i pri svakom pozivu te funkcije poziva se BuiltIn funkcija, s druge strane moguce je definisati promenljivu da se zove kao BuiltIn funkcija i promenljiva ima prioritet. (TODO)
            exp.Called je expression, a ne identifier, moze s.f(5), a to je call(call(., s, f), 5)*/
            _tempAns.Clear();
            
            IQLabValue calledVariable;
            FunctionImplementation calledFunction;
            EngineVisitor tempVisitor;
            int[][] subCoordinates;

            if (exp.Called is Identifier)
            {
                calledVariable = _variableTable.LookUp((Identifier)exp.Called);
                if (calledVariable != null) // exp is variable
                {
                    // can't handle variable
                    if (_isFunctionHandle)
                        throw new EngineException("Error: " + ((Identifier)exp.Called).ID + " was previously used as a variable, conflicting with its use here as the name of a function or command.");
                    if (IsMatrix(calledVariable)) // exp is matrix variable, call is getting submatrix
                    {
                        if (exp.ArgumentList.Length == 0) // no arguments, return whole matrix
                            _tempAns.Add(calledVariable);

                        else // more arguments, get submatrix
                        {
                            subCoordinates = GetSubIndex(exp.ArgumentList);
                            List<int[]> tempSubCoordinates = subCoordinates.ToList();

                            switch (calledVariable.GetEnumType())
                            {
                                case IQLabValueType.QBooleanNDimArray:
                                    for (int i = ((QBooleanNDimArray)calledVariable).Dimension; i < subCoordinates.Count(); i++)
                                    {
                                        tempSubCoordinates.RemoveAt(((QBooleanNDimArray)calledVariable).Dimension);
                                        if (!AllZeros(subCoordinates[i]))
                                            throw new EngineException("Index exceeds matrix dimension");
                                    }

                                    subCoordinates = tempSubCoordinates.ToArray();
                                    try
                                    {
                                        _tempAns.Add(((QBooleanNDimArray)calledVariable).GetSubArray(subCoordinates));
                                    }
                                    catch (NumericalDataRepException e)
                                    {
                                        throw new EngineException(e.NumericalDataRepExceptionMessage);
                                    }
                                    break;
                                case IQLabValueType.QCharNDimArray:
                                    for (int i = ((QCharNDimArray)calledVariable).Dimension; i < subCoordinates.Count(); i++)
                                    {
                                        tempSubCoordinates.RemoveAt(((QCharNDimArray)calledVariable).Dimension);
                                        if (!AllZeros(subCoordinates[i]))
                                            throw new EngineException("Index exceeds matrix dimension");
                                    }

                                    subCoordinates = tempSubCoordinates.ToArray();
                                    try
                                    {
                                        _tempAns.Add(((QCharNDimArray)calledVariable).GetSubArray(subCoordinates));
                                    }
                                    catch (NumericalDataRepException e)
                                    {
                                        throw new EngineException(e.NumericalDataRepExceptionMessage);
                                    }
                                    break;
                                case IQLabValueType.QInt64NDimArray:
                                    for (int i = ((QInt64NDimArray)calledVariable).Dimension; i < subCoordinates.Count(); i++)
                                    {
                                        tempSubCoordinates.RemoveAt(((QInt64NDimArray)calledVariable).Dimension);
                                        if (!AllZeros(subCoordinates[i]))
                                            throw new EngineException("Index exceeds matrix dimension");
                                    }
                                    subCoordinates = tempSubCoordinates.ToArray();
                                    try
                                    {
                                        _tempAns.Add(((QInt64NDimArray)calledVariable).GetSubArray(subCoordinates));
                                    }
                                    catch (NumericalDataRepException e)
                                    {
                                        throw new EngineException(e.NumericalDataRepExceptionMessage);
                                    }
                                    break;
                                case IQLabValueType.QUInt64NDimArray:
                                    for (int i = ((QUInt64NDimArray)calledVariable).Dimension; i < subCoordinates.Count(); i++)
                                    {
                                        tempSubCoordinates.RemoveAt(((QUInt64NDimArray)calledVariable).Dimension);
                                        if (!AllZeros(subCoordinates[i]))
                                            throw new EngineException("Index exceeds matrix dimension");
                                    }

                                    subCoordinates = tempSubCoordinates.ToArray();
                                    try
                                    {
                                        _tempAns.Add(((QUInt64NDimArray)calledVariable).GetSubArray(subCoordinates));
                                    }
                                    catch (NumericalDataRepException e)
                                    {
                                        throw new EngineException(e.NumericalDataRepExceptionMessage);
                                    }
                                    break;
                                case IQLabValueType.QFloat64NDimArray:
                                    for (int i = ((QFloat64NDimArray)calledVariable).Dimension; i < subCoordinates.Count(); i++)
                                    {
                                        tempSubCoordinates.RemoveAt(((QFloat64NDimArray)calledVariable).Dimension);
                                        if (!AllZeros(subCoordinates[i]))
                                            throw new EngineException("Index exceeds matrix dimension");
                                    }

                                    subCoordinates = tempSubCoordinates.ToArray();
                                    try
                                    {
                                        _tempAns.Add(((QFloat64NDimArray)calledVariable).GetSubArray(subCoordinates));
                                    }
                                    catch (NumericalDataRepException e)
                                    {
                                        throw new EngineException(e.NumericalDataRepExceptionMessage);
                                    }
                                    break;
                                case IQLabValueType.QStructValueNDimArray:
                                    for (int i = ((QStructValueNDimArray)calledVariable).Dimension; i < subCoordinates.Count(); i++)
                                    {
                                        tempSubCoordinates.RemoveAt(((QStructValueNDimArray)calledVariable).Dimension);
                                        if (!AllZeros(subCoordinates[i]))
                                            throw new EngineException("Index exceeds matrix dimension");
                                    }

                                    subCoordinates = tempSubCoordinates.ToArray();
                                    try
                                    {
                                        _tempAns.Add(((QStructValueNDimArray)calledVariable).GetSubArray(subCoordinates));
                                    }
                                    catch (NumericalDataRepException e)
                                    {
                                        throw new EngineException(e.NumericalDataRepExceptionMessage);
                                    }
                                    break;
                            }
                            return;
                        }
                    }
                    else if (calledVariable is QCell) // exp is cell
                    {
                        // TODO: get subcell
                    }
                    else if (calledVariable is QFunctionHandle) // exp is function handle
                    {
                        Call tempCall = new Call(((QFunctionHandle)calledVariable).GetFunctionHandle(), exp.ArgumentList);
                        _isFunctionHandle = true;
                        tempCall.Accept(this);
                    }
                    else if (calledVariable is QAnonymousFunction) // exp is anonymous function
                    {
                        //TODO: call AnonymousFunction
                    }
                    return;
                }
                // search other tables
                calledFunction = _nestedFuncTable.LookUp((Identifier)exp.Called);
                if (calledFunction != null) // exp is nested function, also must be matlab function
                {
                    Statement function = ((MatlabFunction)calledFunction).GetFunctionStatement();

                    // initialization of temporary visitor
                    tempVisitor = new EngineVisitor();
                    
                    // preparing tables for visitor
                    tempVisitor._variableTable = (VarTable)_variableTable.Clone();
                    tempVisitor._subFuncTable = (FunctionTable)_subFuncTable.Clone();
                    tempVisitor._subFuncTable.AddFunctions(_nestedFuncTable);
                    tempVisitor._privateFuncTable = (FunctionTable)_privateFuncTable;
                    tempVisitor.AddNestedFunctions((FunctionDefinitionStatement)function);

                    // add parameters as variables
                    for (int i = 0; i < ((FunctionDefinitionStatement)function).ParameterList.Length; i++)
                    {
                        exp.ArgumentList[i].Accept(this);
                        tempVisitor._variableTable.AddVariable(((FunctionDefinitionStatement)function).ParameterList[i], _tempAns[0]);
                        _tempAns.Clear();
                    }

                    for (int i = 0; i < ((FunctionDefinitionStatement)function).ReturnList.Length; i++)
                    {
                        tempVisitor._variableTable.AddVariable(((FunctionDefinitionStatement)function).ReturnList[i], null);
                    }
                    ((FunctionDefinitionStatement)function).FunctionBody.Accept(tempVisitor);
                    tempVisitor._tempAns.ForEach((item) =>
                    {
                        _tempAns.Add((IQLabValue)item.Clone());
                    });

                    _variableTable.MergeVariables(tempVisitor._variableTable, ((FunctionDefinitionStatement)function).ParameterList, ((FunctionDefinitionStatement)function).ReturnList);
                    return;
                }
                calledFunction = LookUpOtherTables((Identifier)exp.Called);
                if (calledFunction is MatlabFunction) // function is matlab function
                {
                    // initialization of temporary visitor
                    tempVisitor = new EngineVisitor();

                    // preparing tables for visitor
                    Statement function = ((MatlabFunction)calledFunction).GetFunctionStatement();
                    if ((function is BlockStatement) && !(((BlockStatement)function).StatementList[0] is FunctionDefinitionStatement))
                    {
                        ((BlockStatement)function).Accept(this);
                    }
                    else
                    {
                        if (function is BlockStatement)
                        {
                            tempVisitor.AddNestedFunctions((FunctionDefinitionStatement)((BlockStatement)function).StatementList[0]);
                            tempVisitor.AddSubFunctions((BlockStatement)function);

                            // first statment of block statement is wanted function
                            function = ((BlockStatement)function).StatementList[0];
                        }
                        else
                        {
                            tempVisitor.AddNestedFunctions((FunctionDefinitionStatement)function);
                        }

                        // add parameters as arguments
                        for (int i = 0; i < ((FunctionDefinitionStatement)function).ParameterList.Length; i++)
                        {
                            exp.ArgumentList[i].Accept(this);
                            tempVisitor._variableTable.AddVariable(((FunctionDefinitionStatement)function).ParameterList[i], _tempAns[0]);
                            _tempAns.Clear();
                        }

                        ((FunctionDefinitionStatement)function).FunctionBody.Accept(tempVisitor);
                        foreach (Identifier ID in ((FunctionDefinitionStatement)function).ReturnList)
                        {
                            _tempAns.Add(tempVisitor._variableTable.LookUp(ID));
                        }
                    }
                }
                else // function is builtin function
                {
                    List<IQLabValue> args=new List<IQLabValue>();

                    // add parameters as arguments
                    LeftHandSideCount(args, ((Identifier)exp.Called).ID);
                    for (int i = 0; i < exp.ArgumentList.Count(); i++)
                    {
                        exp.ArgumentList[i].Accept(this);
                        args.Add(_tempAns[0]);
                        _tempAns.Clear();
                    }

                    _tempAns=((BuiltInFunction)calledFunction).f(args);
                }
            }
            else if (exp.Called is Call)
            {
                // TODO: TEST!!!
                ((Call)exp.Called).Accept(this);
                _variableTable.AddVariable(new Identifier("_"), _tempAns[0]);
                _tempAns.Clear();

                Call tempCall = new Call(new Identifier("_"), ((Call)exp).ArgumentList);
                tempCall.Accept(this);
                _variableTable.RemoveVariable(new Identifier("_"));
            }
        }
                  

        public void Visit(NDimArrayBuilder exp)
        {
            _tempAns.Clear();

            IQLabValue tempHFieldOld, tempHField, tempMatrix;
            NDimArray<QFloat64> tempFloat;
            NDimArray<QInt64> tempInt;
            bool isFloat = false;
            
            // initialize first row

            // intialize first field in row

            if (exp.Exprs.Length == 0)
            {
                tempFloat = new NDimArray<QFloat64>();
                _tempAns.Add(new QFloat64NDimArray(tempFloat.Data, tempFloat.DimensionSize));
                return;
            }
            exp.Exprs[0][0].Accept(this);
            if (_tempAns[0] is QFloat64NDimArray)
            {
                tempHFieldOld = (QFloat64NDimArray)_tempAns[0];
                isFloat = true;
            }
            else
                tempHFieldOld = (QInt64NDimArray)_tempAns[0];
            _tempAns.Clear();

            // concatenate fields in row
            for (int i = 1; i < exp.Exprs[0].Count(); i++)
            {                
                exp.Exprs[0][i].Accept(this);
                if (isFloat || (_tempAns[0] is QFloat64NDimArray))
                {
                    // TODO: if isFloat is now changed, cast old matrix fields
                    tempHField = (QFloat64NDimArray)_tempAns[0];
                    tempFloat = NDimArray<QFloat64>.Concatenate(1, (QFloat64NDimArray)tempHFieldOld, (QFloat64NDimArray)tempHField);// tempHFieldOld is horizontal concat tempHFieldOld and tempHField
                    tempHFieldOld = new QFloat64NDimArray(tempFloat.Data, tempFloat.DimensionSize);
                    isFloat = true;
                }
                else
                {
                    tempHField = (QInt64NDimArray)_tempAns[0];
                    tempInt = NDimArray<QInt64>.Concatenate(1, (QInt64NDimArray)tempHFieldOld, (QInt64NDimArray)tempHField);// tempHFieldOld is horizontal concat tempHFieldOld and tempHField
                    tempHFieldOld = new QInt64NDimArray(tempInt.Data, tempInt.DimensionSize);
                }
                _tempAns.Clear();
            }

            if (isFloat)
                tempMatrix = (QFloat64NDimArray)tempHFieldOld;
            else
                tempMatrix = (QInt64NDimArray)tempHFieldOld;

            // build matrix
            for (int i=1; i<exp.Exprs.Count(); i++)
            {
                // intialize first field in row
                exp.Exprs[i][0].Accept(this);
                if (isFloat || (_tempAns[0] is QFloat64NDimArray))
                {
                    // TODO: if isFloat is now changed, cast old matrix fields
                    tempHFieldOld = (QFloat64NDimArray)_tempAns[0];
                    isFloat = true;
                }
                else
                    tempHFieldOld = (QInt64NDimArray)_tempAns[0];

                _tempAns.Clear();

                // concatenate fields in row
                for (int j = 1; j < exp.Exprs[i].Count(); j++)
                {
                    exp.Exprs[i][j].Accept(this);

                    if (isFloat || (_tempAns[0] is QFloat64NDimArray))
                    {
                        // TODO: if isFloat is now changed, cast old matrix fields
                        tempHField = (QFloat64NDimArray)_tempAns[0];
                        tempFloat = NDimArray<QFloat64>.Concatenate(1, (QFloat64NDimArray)tempHFieldOld, (QFloat64NDimArray)tempHField);// tempHFieldOld is horizontal concat tempHFieldOld and tempHField
                        tempHFieldOld = new QFloat64NDimArray(tempFloat.Data, tempFloat.DimensionSize);
                        isFloat = true;
                    }
                    else
                    {
                        tempHField = (QInt64NDimArray)_tempAns[0];
                        tempInt = NDimArray<QInt64>.Concatenate(1, (QInt64NDimArray)tempHFieldOld, (QInt64NDimArray)tempHField);// tempHFieldOld is horizontal concat tempHFieldOld and tempHField
                        tempHFieldOld = new QInt64NDimArray(tempInt.Data, tempInt.DimensionSize);
                    }

                    _tempAns.Clear();
                }
                
                // concatenate rows in matrix 
                if (isFloat)
                {
                    tempFloat = NDimArray<QFloat64>.Concatenate(0, (QFloat64NDimArray)tempMatrix, (QFloat64NDimArray)tempHFieldOld);
                    tempMatrix = new QFloat64NDimArray(tempFloat.Data, tempFloat.DimensionSize);
                }
                else
                {
                    tempInt = NDimArray<QInt64>.Concatenate(0, (QInt64NDimArray)tempMatrix, (QInt64NDimArray)tempHFieldOld);
                    tempMatrix = new QInt64NDimArray(tempInt.Data, tempInt.DimensionSize);
                }
            }

            _tempAns.Add(tempMatrix);
        }

        public void Visit(SubCell exp)
        {
            _tempAns.Clear();
            if (exp.CellIdentifier is Identifier)
            {
                IQLabValue cell = _variableTable.LookUp((Identifier)exp.CellIdentifier);
                if (cell is QCell)
                {
                    int[][] subCoordinates = GetSubIndex(exp.ArgumentList);
                    if (exp.ArgumentList.Count() > ((QCell)cell).Dimension)
                    {
                        for (int i = ((QCell)cell).Dimension; i < exp.ArgumentList.Count(); i++)
                            if (!AllZeros(subCoordinates[i]))
                                throw new Exception("Index exceeds matrix dimensions");
                    }
                    if (exp.ArgumentList.Count() < ((QCell)cell).Dimension)
                    {
                        for (int i = exp.ArgumentList.Count(); i < ((QCell)cell).Dimension; i++)
                            subCoordinates[i] = new int[] { 1 };
                    }
                    _tempAns=(((QCell)cell).GetSubArray(subCoordinates)).Data.ToList();
                }
                else
                    throw new EngineException("Cell contents reference from a non-cell array object");
            }
        }

        public void Visit(AnonymousFunctionBuilder exp)
        {
            // TODO: anonymous function visit
            throw new NotImplementedException();
        }

        public void Visit(CellBuilder exp)
        {
            // TODO: cellbuilder visit
            throw new NotImplementedException();
        }

        public void Visit(SingleExpressionStatement stmt)
        {
            _tempAns.Clear();

            stmt.Expr.Accept(this);
        }

        public void Visit(AssignmentStatement stmt)
        {
            _tempAns.Clear();

            _leftHandSideCount = stmt.LeftHandSide.Count();

            stmt.RightHandSide.Accept(this);
            List<IQLabValue> rightHandResults = new List<IQLabValue>();
            _tempAns.ForEach((item) =>
                {
                    rightHandResults.Add((IQLabValue)item.Clone());
            });
            for (int i = 0; i < stmt.LeftHandSide.Length; i++)
            {
                Expression id = stmt.LeftHandSide[i];

                if (id is Identifier)
                    _variableTable.AddVariable((Identifier)id, rightHandResults[i]);
                else if (id is Call) // need fix if there is one arg for submatrix
                {
                    if (_variableTable.LookUp((Identifier)((Call)id).Called) == null)
                        switch (rightHandResults[i].GetEnumType())
                        {
                            case IQLabValueType.QBooleanNDimArray:
                                _variableTable.AddVariable((Identifier)((Call)id).Called, new QBooleanNDimArray(new int[2] { 1, 1 }));
                                break;
                            case IQLabValueType.QCharNDimArray:
                                _variableTable.AddVariable((Identifier)((Call)id).Called, new QCharNDimArray(new int[2] { 1, 1 }));
                                break;
                            case IQLabValueType.QInt64NDimArray:
                                _variableTable.AddVariable((Identifier)((Call)id).Called, new QInt64NDimArray(new int[2] { 1, 1 }));
                                break;
                            case IQLabValueType.QUInt64NDimArray:
                                _variableTable.AddVariable((Identifier)((Call)id).Called, new QUInt64NDimArray(new int[2] { 1, 1 }));
                                break;
                            case IQLabValueType.QFloat64NDimArray:
                                _variableTable.AddVariable((Identifier)((Call)id).Called, new QFloat64NDimArray(new int[2] { 1, 1 }));
                                break;
                            case IQLabValueType.QStructValueNDimArray:
                                _variableTable.AddVariable((Identifier)((Call)id).Called, new QStructValueNDimArray(new int[2] { 1, 1 }));
                                break;
                            case IQLabValueType.QCell:
                                _variableTable.AddVariable((Identifier)((Call)id).Called, new QCell(new int[2] { 1, 1 }));
                                break;
                        }
                    IQLabValue calledVariable = _variableTable.LookUp((Identifier)((Call)id).Called);
                    if (((Call)id).ArgumentList.Length == 0) 
                        throw new EngineException("An indexing expression on the left side of an assignment must have at least one subscript.");
                    else
                    {
                        int[][] subCoordinates = GetSubIndex(((Call)id).ArgumentList);
                        IQLabValue tempMatrix;
                        List<int[]> tempSubCoordinates = subCoordinates.ToList();

                        switch (calledVariable.GetEnumType())
                        {
                            case IQLabValueType.QBooleanNDimArray:
                                for (int j = ((QBooleanNDimArray)calledVariable).Dimension; j < subCoordinates.Count(); j++)
                                {
                                    tempSubCoordinates.RemoveAt(((QBooleanNDimArray)calledVariable).Dimension);
                                    if (!AllZeros(subCoordinates[j]))
                                        throw new EngineException("Index exceeds matrix dimension");
                                }

                                subCoordinates = tempSubCoordinates.ToArray();
                                tempMatrix = new QBooleanNDimArray(rightHandResults[i]);
                                _variableTable.AddVariable((Identifier)((Call)id).Called, ((QBooleanNDimArray)calledVariable).SubAssignment((QBooleanNDimArray)tempMatrix, subCoordinates));
                                break;
                            case IQLabValueType.QCharNDimArray:
                                for (int j = ((QCharNDimArray)calledVariable).Dimension; j < subCoordinates.Count(); j++)
                                {
                                    tempSubCoordinates.RemoveAt(((QCharNDimArray)calledVariable).Dimension);
                                    if (!AllZeros(subCoordinates[j]))
                                        throw new EngineException("Index exceeds matrix dimension");
                                }

                                subCoordinates = tempSubCoordinates.ToArray();
                                tempMatrix = new QCharNDimArray(rightHandResults[i]);
                                _variableTable.AddVariable((Identifier)((Call)id).Called, ((QCharNDimArray)calledVariable).SubAssignment((QCharNDimArray)tempMatrix, subCoordinates));
                                break;
                            case IQLabValueType.QInt64NDimArray:
                                for (int j = ((QInt64NDimArray)calledVariable).Dimension; j < subCoordinates.Count(); j++)
                                {
                                    tempSubCoordinates.RemoveAt(((QInt64NDimArray)calledVariable).Dimension);
                                    if (!AllZeros(subCoordinates[j]))
                                        throw new EngineException("Index exceeds matrix dimension");
                                }
                                subCoordinates = tempSubCoordinates.ToArray();
                                tempMatrix = new QInt64NDimArray(rightHandResults[i]);
                                _variableTable.AddVariable((Identifier)((Call)id).Called, ((QInt64NDimArray)calledVariable).SubAssignment((QInt64NDimArray)tempMatrix, subCoordinates));
                                break;
                            case IQLabValueType.QUInt64NDimArray:
                                for (int j = ((QUInt64NDimArray)calledVariable).Dimension; j < subCoordinates.Count(); j++)
                                {
                                    tempSubCoordinates.RemoveAt(((QUInt64NDimArray)calledVariable).Dimension);
                                    if (!AllZeros(subCoordinates[j]))
                                        throw new EngineException("Index exceeds matrix dimension");
                                }

                                subCoordinates = tempSubCoordinates.ToArray();
                                tempMatrix = new QUInt64NDimArray(rightHandResults[i]);
                                _variableTable.AddVariable((Identifier)((Call)id).Called, ((QUInt64NDimArray)calledVariable).SubAssignment((QUInt64NDimArray)tempMatrix, subCoordinates));
                                break;
                            case IQLabValueType.QFloat64NDimArray:
                                for (int j = ((QFloat64NDimArray)calledVariable).Dimension; j < subCoordinates.Count(); j++)
                                {
                                    tempSubCoordinates.RemoveAt(((QFloat64NDimArray)calledVariable).Dimension);
                                    if (!AllZeros(subCoordinates[j]))
                                        throw new EngineException("Index exceeds matrix dimension");
                                }

                                subCoordinates = tempSubCoordinates.ToArray();
                                tempMatrix = new QFloat64NDimArray(rightHandResults[i]);
                                _variableTable.AddVariable((Identifier)((Call)id).Called, ((QFloat64NDimArray)calledVariable).SubAssignment((QFloat64NDimArray)tempMatrix, subCoordinates));
                                break;
                            case IQLabValueType.QStructValueNDimArray:
                                for (int j = ((QStructValueNDimArray)calledVariable).Dimension; j < subCoordinates.Count(); j++)
                                {
                                    tempSubCoordinates.RemoveAt(((QStructValueNDimArray)calledVariable).Dimension);
                                    if (!AllZeros(subCoordinates[j]))
                                        throw new EngineException("Index exceeds matrix dimension");
                                }

                                subCoordinates = tempSubCoordinates.ToArray();
                                tempMatrix = new QStructValueNDimArray(rightHandResults[i]);
                                _variableTable.AddVariable((Identifier)((Call)id).Called, ((QStructValueNDimArray)calledVariable).SubAssignment((QStructValueNDimArray)tempMatrix, subCoordinates));
                                break;
                            case IQLabValueType.QCell:
                                for (int j = ((QCell)calledVariable).Dimension; j < subCoordinates.Count(); j++)
                                {
                                    tempSubCoordinates.RemoveAt(((QCell)calledVariable).Dimension);
                                    if (!AllZeros(subCoordinates[j]))
                                        throw new EngineException("Index exceeds matrix dimension");
                                }
                                subCoordinates = tempSubCoordinates.ToArray();
                                tempMatrix = new QCell(rightHandResults[i]);
                                _variableTable.AddVariable((Identifier)((Call)id).Called, ((QCell)calledVariable).SubAssignment((QCell)tempMatrix, subCoordinates));
                                break;
                        }
                    }
                }
                else // TODO: check if there are other possibilities
                {
                    throw new NotImplementedException();
                }
            }

            if (stmt.LeftHandSide[0] is Identifier)
                _tempAns.Add(_variableTable.LookUp((Identifier)stmt.LeftHandSide[0]));
            else if (stmt.LeftHandSide[0] is Call)
                _tempAns.Add(_variableTable.LookUp((Identifier)((Call)stmt.LeftHandSide[0]).Called)); // TODO: if called is call

        }

        public void Visit(BlockStatement stmt)
        {
            _tempAns.Clear();

            for (int i = 0; i < stmt.StatementList.Count(); i++)
            {
                stmt.StatementList[i].Accept(this);
                _tempAns.Clear();

                if (_continueFlag || _breakFlag)
                    return;
            }
        }

        public void Visit(IfThenElseStatement stmt)
        {
            _tempAns.Clear();

            stmt.Condition.Accept(this);
            bool exp = true;
            switch (_tempAns[0].GetEnumType())
            {
                case IQLabValueType.QBooleanNDimArray:
                    exp = ((QBooleanNDimArray)_tempAns[0]).LogicalValue();
                    break;
                case IQLabValueType.QCharNDimArray:
                    exp = ((QCharNDimArray)_tempAns[0]).LogicalValue();
                    break;
                case IQLabValueType.QInt64NDimArray:
                    exp = ((QInt64NDimArray)_tempAns[0]).LogicalValue();
                    break;
                case IQLabValueType.QUInt64NDimArray:
                    exp = ((QUInt64NDimArray)_tempAns[0]).LogicalValue();
                    break;
                case IQLabValueType.QFloat64NDimArray:
                    exp = ((QFloat64NDimArray)_tempAns[0]).LogicalValue();
                    break;
                case IQLabValueType.QStructValueNDimArray:
                    throw new EngineException("Conversion to logical from struct is not possible.");
                case IQLabValueType.QCell:
                    throw new EngineException("Conversion to logical from cell is not possible.");
                case IQLabValueType.QFunctionHandle:
                    throw new EngineException("Conversion to logical from function_handle is not possible.");
            }
            _tempAns.Clear();

            if (exp)
                stmt.IfConditionTrueStatement.Accept(this);
            else
                stmt.IfConditionFalseStatement.Accept(this);

        }

        public void Visit(SwitchCaseStatement stmt)
        {
            _tempAns.Clear();

            stmt.Expr.Accept(this);
            IAtomicType exp = null;
            switch (_tempAns[0].GetEnumType())
            {
                case IQLabValueType.QBooleanNDimArray:
                    if (!AllOnes(((QBooleanNDimArray)_tempAns[0]).DimensionSize))
                        throw new EngineException("SWITCH expression must be a scalar or string constant.");
                    exp = ((QBooleanNDimArray)_tempAns[0]).Data[0];
                    break;
                case IQLabValueType.QCharNDimArray:
                    if (!AllOnes(((QCharNDimArray)_tempAns[0]).DimensionSize))
                        throw new EngineException("SWITCH expression must be a scalar or string constant.");
                    exp = ((QCharNDimArray)_tempAns[0]).Data[0];
                    break;
                case IQLabValueType.QInt64NDimArray:
                    if (!AllOnes(((QInt64NDimArray)_tempAns[0]).DimensionSize))
                        throw new EngineException("SWITCH expression must be a scalar or string constant.");
                    exp = ((QInt64NDimArray)_tempAns[0]).Data[0];
                    break;
                case IQLabValueType.QUInt64NDimArray:
                    if (!AllOnes(((QUInt64NDimArray)_tempAns[0]).DimensionSize))
                        throw new EngineException("SWITCH expression must be a scalar or string constant.");
                    exp = ((QUInt64NDimArray)_tempAns[0]).Data[0];
                    break;
                case IQLabValueType.QFloat64NDimArray:
                    if (!AllOnes(((QFloat64NDimArray)_tempAns[0]).DimensionSize))
                        throw new EngineException("SWITCH expression must be a scalar or string constant.");
                    exp = ((QFloat64NDimArray)_tempAns[0]).Data[0];
                    break;
                case IQLabValueType.QStructValueNDimArray:
                    throw new EngineException("Conversion to logical from struct is not possible.");
                case IQLabValueType.QCell:
                    throw new EngineException("Conversion to logical from cell is not possible.");
                case IQLabValueType.QFunctionHandle:
                    throw new EngineException("Conversion to logical from function_handle is not possible.");
            }
            _tempAns.Clear();

            IAtomicType caseExp;
            for (int i = 0; i < stmt.Cases.Count(); i++)
            {
                stmt.Cases[i].Item1.Accept(this);
                switch (_tempAns[0].GetEnumType())
                {
                    case IQLabValueType.QBooleanNDimArray:
                        if (!AllOnes(((QBooleanNDimArray)_tempAns[0]).DimensionSize))
                            break;
                        caseExp = ((QBooleanNDimArray)_tempAns[0]).Data[0];
                        if (exp.Equals(caseExp))
                        {
                            _tempAns.Clear();
                            stmt.Cases[i].Item2.Accept(this);
                            return;
                        }
                        break;
                    case IQLabValueType.QCharNDimArray:
                        if (!AllOnes(((QCharNDimArray)_tempAns[0]).DimensionSize))
                            break;
                        caseExp = ((QCharNDimArray)_tempAns[0]).Data[0];
                        if (exp.Equals(caseExp))
                        {
                            _tempAns.Clear();
                            stmt.Cases[i].Item2.Accept(this);
                            return;
                        }
                        break;
                    case IQLabValueType.QInt64NDimArray:
                        if (!AllOnes(((QInt64NDimArray)_tempAns[0]).DimensionSize))
                            break;
                        caseExp = ((QInt64NDimArray)_tempAns[0]).Data[0];
                        if (exp.Equals(caseExp))
                        {
                            _tempAns.Clear();
                            stmt.Cases[i].Item2.Accept(this);
                            return;
                        }
                        break;
                    case IQLabValueType.QUInt64NDimArray:
                        if (!AllOnes(((QUInt64NDimArray)_tempAns[0]).DimensionSize))
                            break;
                        caseExp = ((QUInt64NDimArray)_tempAns[0]).Data[0];
                        if (exp.Equals(caseExp))
                        {
                            _tempAns.Clear();
                            stmt.Cases[i].Item2.Accept(this);
                            return;
                        }
                        break;
                    case IQLabValueType.QFloat64NDimArray:
                        if (!AllOnes(((QFloat64NDimArray)_tempAns[0]).DimensionSize))
                            break;
                        caseExp = ((QFloat64NDimArray)_tempAns[0]).Data[0];
                        if (exp.Equals(caseExp))
                        {
                            _tempAns.Clear();
                            stmt.Cases[i].Item2.Accept(this);
                            return;
                        }
                        break;
                }
                _tempAns.Clear();
            }

            stmt.Otherwise.Accept(this);
        }

        public void Visit(ForStatement stmt)
        {
            _tempAns.Clear();

            stmt.Expr.Accept(this);
            IQLabValue loopArray = _tempAns[0];
            _tempAns.Clear();

            switch (loopArray.GetEnumType())
            {
                case IQLabValueType.QBooleanNDimArray:
                    for (int i=0; i<((QBooleanNDimArray)loopArray).DimensionSize[0]; i++)
                    {
                        _variableTable.AddVariable(stmt.ID, ((QBooleanNDimArray)loopArray).GetSubArray(new int[][]{ new int[]{i}, new int[]{1}}));
                        stmt.LoopStatement.Accept(this);
                        _tempAns.Clear();
                        if (_continueFlag)
                            _continueFlag = false;
                        if (_breakFlag)
                        {
                            _breakFlag = false;
                            break;
                        }
                    }
                    break;
                case IQLabValueType.QCharNDimArray:
                    for (int i=0; i<((QCharNDimArray)loopArray).DimensionSize[0]; i++)
                    {
                        _variableTable.AddVariable(stmt.ID, ((QCharNDimArray)loopArray).GetSubArray(new int[][]{ new int[]{i}, new int[]{1}}));
                        stmt.LoopStatement.Accept(this);
                        _tempAns.Clear();
                        if (_continueFlag)
                            _continueFlag = false;
                        if (_breakFlag)
                        {
                            _breakFlag = false;
                            break;
                        }
                    }
                    break;
                case IQLabValueType.QInt64NDimArray:
                    for (int i=0; i<((QInt64NDimArray)loopArray).DimensionSize[0]; i++)
                    {
                        _variableTable.AddVariable(stmt.ID, ((QInt64NDimArray)loopArray).GetSubArray(new int[][]{ new int[]{i}, new int[]{1}}));
                        stmt.LoopStatement.Accept(this);
                        _tempAns.Clear();
                        if (_continueFlag)
                            _continueFlag = false;
                        if (_breakFlag)
                        {
                            _breakFlag = false;
                            break;
                        }
                    }
                    break;
                case IQLabValueType.QUInt64NDimArray:
                    for (int i=0; i<((QUInt64NDimArray)loopArray).DimensionSize[0]; i++)
                    {
                        _variableTable.AddVariable(stmt.ID, ((QUInt64NDimArray)loopArray).GetSubArray(new int[][]{ new int[]{i}, new int[]{1}}));
                        stmt.LoopStatement.Accept(this);
                        _tempAns.Clear();
                        if (_continueFlag)
                            _continueFlag = false;
                        if (_breakFlag)
                        {
                            _breakFlag = false;
                            break;
                        }
                    }
                    break;
                case IQLabValueType.QStructValueNDimArray:
                    for (int i=0; i<((QStructValueNDimArray)loopArray).DimensionSize[0]; i++)
                    {
                        _variableTable.AddVariable(stmt.ID, ((QStructValueNDimArray)loopArray).GetSubArray(new int[][]{ new int[]{i}, new int[]{1}}));
                        stmt.LoopStatement.Accept(this);
                        _tempAns.Clear();
                        if (_continueFlag)
                            _continueFlag = false;
                        if (_breakFlag)
                        {
                            _breakFlag = false;
                            break;
                        }
                    }
                    break;
                case IQLabValueType.QCell:
                    for (int i=0; i<((QCell)loopArray).DimensionSize[0]; i++)
                    {
                        _variableTable.AddVariable(stmt.ID, ((QCell)loopArray).GetSubArray(new int[][]{ new int[]{i}, new int[]{1}}));
                        stmt.LoopStatement.Accept(this);
                        _tempAns.Clear();
                        if (_continueFlag)
                            _continueFlag = false;
                        if (_breakFlag)
                        {
                            _breakFlag = false;
                            break;
                        }
                    }
                    break;
            }
        }

        public void Visit(WhileStatement stmt)
        {
            _tempAns.Clear();

            bool stopFlag = false;
            
            while (true)
            {
                stmt.Condition.Accept(this);
                switch (_tempAns[0].GetEnumType())
                {
                    case IQLabValueType.QBooleanNDimArray:
                        if (!((QBooleanNDimArray)_tempAns[0]).LogicalValue())
                            stopFlag = true;
                        break;
                    case IQLabValueType.QCharNDimArray:
                        if (!((QCharNDimArray)_tempAns[0]).LogicalValue())
                            stopFlag = true;
                        break;
                    case IQLabValueType.QInt64NDimArray:
                        if (!((QInt64NDimArray)_tempAns[0]).LogicalValue())
                            stopFlag = true;
                        break;
                    case IQLabValueType.QUInt64NDimArray:
                        if (!((QUInt64NDimArray)_tempAns[0]).LogicalValue())
                            stopFlag = true;
                        break;
                    case IQLabValueType.QFloat64NDimArray:
                        if (!((QFloat64NDimArray)_tempAns[0]).LogicalValue())
                            stopFlag = true;
                        break;               
                }
                _tempAns.Clear();

                if (stopFlag)
                    break;

                stmt.LoopStatement.Accept(this);
            }
        }

        public void Visit(ContinueStatement stmt)
        {
            _continueFlag = true;
        }

        public void Visit(BreakStatement stmt)
        {
            _breakFlag = true;
        }

        public void Visit(TryCatchStatement stmt)
        {
            throw new NotImplementedException();
        }

        public void Visit(ReturnStatement stmt)
        {
            //TODO: mislite o tome :D, zaustavlja obilazak
            throw new NotImplementedException();
        }

        public void Visit(FunctionDefinitionStatement stmt)
        {
            AddNestedFunctions(stmt);
            for (int i = 0; i < stmt.ReturnList.Length; i++)
            {
                _variableTable.AddVariable(stmt.ReturnList[i], null);
            }
        }

        public void Visit(GlobalDeclarationStatement stmt)
        {
            throw new NotImplementedException();
        }

        public void Visit(PersistentDeclarationStatement stmt)
        {
            throw new NotImplementedException();
        }
    }
}
